#ifndef MAIN_H
#define MAIN_H

// CONFIG1
#pragma config FEXTOSC = ECH    // External Oscillator mode selection bits (EC above 8MHz; PFM set to high power)
#pragma config RSTOSC = HFINT32 // Power-up default value for COSC bits (HFINTOSC (1MHz))
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; i/o or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable bit (FSCM timer enabled)

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit (MCLR pin is Master Clear function)
#pragma config PWRTE = OFF   // Power-up Timer Enable bit (PWRT disabled)
#pragma config LPBOREN = OFF // Low-Power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON    // Brown-out reset enable bits (Brown-out Reset Enabled, SBOREN bit is ignored)
#pragma config BORV = LO     // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices)
#pragma config ZCD = OFF     // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR.)
#pragma config PPS1WAY = OFF // Peripheral Pin Select one-way control (The PPSLOCK bit can be set and cleared repeatedly by software)
#pragma config STVREN = ON   // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset)

// CONFIG3
#pragma config WDTCPS = WDTCPS_31 // WDT Period Select bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = SWDTEN      // WDT operating mode (WDT enabled/disabled by SWDTEN bit in WDTCON0)
#pragma config WDTCWS = WDTCWS_7  // WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC        // WDT input clock selector (Software Control)

// CONFIG4
#pragma config WRT = WRT_upper       // UserNVM self-write protection bits (0x0000 to 0x01FF write protected)
#pragma config SCANE = not_available // Scanner Enable bit (Scanner module is not available for use)
#pragma config LVP = ON              // Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/Vpp pin function is MCLR.)

// CONFIG5
#pragma config CP = OFF  // UserNVM Program memory code protection bit (Program Memory code protection disabled)
#pragma config CPD = OFF // DataNVM code protection bit (Data EEPROM code protection disabled)

#define FOSC 32000000
#define _XTAL_FREQ 32000000
#include <stdint.h>
#include <xc.h>
#include <eeprom_routines.h>

//--------------------------TYPEDEF---------------------------
typedef void (*key_handler)();

typedef struct
{
    uint8_t b0;
    uint8_t b1;
} tbit_t;

//--------------------------DISPLAY---------------------------
const uint8_t segcode_table[16] = {
    0b00111111,
    0b00000110,
    0b01011011,
    0b01001111,
    0b01100110,
    0b01101101,
    0b01111101,
    0b00000111,
    0b01111111,
    0b01101111,
    0b01110111,
    0b01111100,
    0b00111001,
    0b01011110,
    0b01111001,
    0b01110001,
};
#define SEGERR 0b01000000
#define SEGNULL 0
uint8_t display[4] = {0, 0, 0, 0};
uint8_t blink = 0, blinkmask = 0;
uint8_t bitpos = 0, segcode = 0, bitcode = 0;

//--------------------------UTILS---------------------------
uint8_t i, j, k, l;
#define LED_ON LATBbits.LATB5 = 0
#define LED_OFF LATBbits.LATB5 = 1
#define ALARM_ON LATBbits.LATB4 = 1
#define ALARM_OFF LATBbits.LATB4 = 0
#define AT_ON LATBbits.LATB1 = 0
#define AT_OFF LATBbits.LATB1 = 1

void reset()
{
    asm("reset");
}

void nop()
{
}

static uint8_t rnd_seed;

uint8_t rnd()
{
    rnd_seed ^= TMR0L;
    rnd_seed += ~(rnd_seed << 3);
    return rnd_seed;
}
#define MIN_ID 16
#define MAX_ID 254

uint8_t genid()
{
    while (rnd_seed > MAX_ID || rnd_seed < MIN_ID)
        rnd();
    return rnd_seed;
}

void err_rst()
{
    LED_OFF;
    ALARM_ON;
    __delay_ms(50);
    asm("reset");
}

//--------------------------TIME---------------------------
#define TIME_NUM 4
tbit_t local_times[TIME_NUM + 2] = {0}, cluster_times[TIME_NUM + 2] = {0};
uint8_t local_time_num = 0, cluster_time_num = 0;
uint8_t local_cur_timer = 0, cluster_cur_timer = 0;

#define emp_bits 2
#define use_bits 6
#define tick_delta 4 //! WARNING: DO NOT CHANGE!!!
uint8_t tick = 0;
tbit_t ts;

void tbit_inc(tbit_t *tb, uint8_t l1, uint8_t l0)
{
    if (tb->b0 < l0)
        ++tb->b0;
    else if (tb->b1 < l1)
    {
        tb->b0 = 0;
        ++tb->b1;
    }
}

void tbit_dec(tbit_t *tb, uint8_t l0, uint8_t m0)
{
    if (tb->b1 == 0 && tb->b0 == m0)
        return;
    if (tb->b0 > 0)
        --tb->b0;
    else if (tb->b1 > 0)
    {
        tb->b0 = l0;
        --tb->b1;
    }
}

void norm_times()
{
    for (int8_t i = 0; i < TIME_NUM; ++i)
        for (int8_t j = i + 1; j < TIME_NUM + 1; ++j)
            if (local_times[i].b1 < local_times[j].b1 || (local_times[i].b1 == local_times[j].b1 && local_times[i].b0 < local_times[j].b0))
            {
                tbit_t tmp = local_times[i];
                local_times[i] = local_times[j];
                local_times[j] = tmp;
            }

    local_time_num = 0;
    for (int8_t i = 1; i < TIME_NUM + 2; ++i)
        if (local_times[local_time_num].b1 != local_times[i].b1 || local_times[local_time_num].b0 != local_times[i].b0)
            local_times[++local_time_num] = local_times[i];
}

void timecpy(uint8_t *dst, uint8_t *src, uint8_t n)
{
    for (i = 0; i < n; ++i)
        dst[i] = src[i];
}

void flush_cur_timer()
{
    cluster_cur_timer = 0;
    while (ts.b1 < cluster_times[cluster_cur_timer].b1 || (ts.b1 == cluster_times[cluster_cur_timer].b1 && ts.b0 < cluster_times[cluster_cur_timer].b0))
        ++cluster_cur_timer;
}

//--------------------------COMMUNICATION---------------------------

inline void init_uart()
{
    RB3PPS = 0x10; // TX
    RXPPS = 0x0a;  //RB2
    BRGH = 1;
    BRG16 = 1;
    SP1BRG = 832; // FOSC/4/Baud
    TX1STAbits.SYNC = 0;
    RC1STAbits.SPEN = 1;
    RC1STAbits.CREN = 1;

    TXIE = 1;
    RCIE = 1;
}

#define BUF_SIZE 32
const uint8_t BUF_MASK = BUF_SIZE - 1;
uint8_t send_buffer[BUF_SIZE];
uint8_t send_head = 0, send_tail = 0;
uint8_t send_buffer_l2[4];
uint8_t tmp_ch = 8;

#define clear_buffer TX1STAbits.TXEN = send_head = send_tail = 0

#define CCS 0x1
#define ACS 0x2
#define DOSEND 0x4

void send(uint8_t *data, uint8_t len, uint8_t ctr)
{
    static uint8_t cs;
    if (ctr & CCS)
        cs = 0;
    for (i = 0; i < len; ++i)
    {
        send_buffer[(send_tail++) & BUF_MASK] = data[i];
        cs ^= data[i];
    }
    if (ctr & ACS)
        send_buffer[(send_tail++) & BUF_MASK] = cs;
    if (ctr & DOSEND)
        TX1STAbits.TXEN = 1;
}

#define RC_DOG packet_timeout = 5
#define PACKET_START 0b10101100
typedef enum
{
    CMD_GETID = 8,
    CMD_CONF,
    CMD_CANDIDATING,
    CMD_SETTING = 16,
    CMD_START = CMD_SETTING,
    CMD_PAUSE,
    CMD_RST,
    CMD_MASK = 31,
    CMD_MHB = 64
} cmd_t;
uint8_t cmd0;
#define RC_IDLE 0
#define RC_CMD 1
#define RC_AT 2
#define RC_SYNC 3
const uint8_t rc_ch_pat[] = {0x4f, 0x4b, 0x2b, 0x52, 0x43, 0x30, 0, 0x31, 0x0d, 0x0a};
uint8_t rc_cs;
uint8_t packet_timeout = 0;
uint8_t packet_rc_cnt = 0;
uint8_t packet_rc_state = RC_IDLE;
uint8_t rc_target;
uint8_t rc_buffer[(TIME_NUM << 2) + 6];

//--------------------------CLUSTER---------------------------
uint8_t device_id;
uint8_t peer_id;

#define POWERON 0x0
#define SLAVE 0x1
#define CANDIDATE 0x2
#define MASTER 0x3
uint8_t cluster_state = POWERON;
uint8_t sc_timeout = 0;                              // SLAVE -> CANDIDATE timeout counter
#define SC_RST sc_timeout = 0xf0 | (device_id & 0xf) // SLAVE -> CANDIDATE timer reset
#define SC_STOP sc_timeout = 0
uint8_t chb_timeout = 0;           // candidate send timeout counter
#define CHB_RST chb_timeout = 0x20 // candidate heart beat send timer reset
#define CHB_STOP chb_timeout = 0
uint8_t cm_timeout = 0; // CANDIDATE -> MASTER timeout counter
#define CM_RST cm_timeout = 0xf0 | (device_id & 0xf)
#define CM_STOP cm_timeout = 0
uint8_t mhb_timeout = 0;
#define MHB_RST mhb_timeout = 0x70 // MASTER heart beat send timer reset
#define MHB_STOP mhb_timeout = 0

uint8_t cluster_ready;

//--------------------------LOCAL---------------------------

typedef enum
{
    IDLE,
    SEL_SETTING,
    SEL_TIMER,
    CHANNEL_SETTING,
    TIME_SETTING_M,
    TIME_SETTING_S,
    SEL_START
} local_state_t;
uint8_t local_state = IDLE; // display state

typedef enum
{
    STOPPED,
    RUNNING,
    PAUSE,
} running_state_t;
uint8_t running_state = IDLE;

typedef enum
{
    CUR_SETTING_BEGIN,
    CLUSTER_SETTING = CUR_SETTING_BEGIN,
    LOCAL_SETTING,
    NEW_SETTING,
    CUR_SETTING_END = NEW_SETTING,
} cur_setting_t;
uint8_t cur_setting;

uint8_t local_rst_timeout = 0;
#define LOCAL_RST local_rst_timeout = 255
#define LOCAL_STOP local_rst_timeout = 0

//--------------------------EVENT---------------------------

#define T40 4
#define T80 8
#define T800 40
#define T400 20

typedef enum
{
    WAIT_AT_ON,
    WAIT_AT_OFF,
    WAIT_ALARM,
    WAIT_GETID,
    WAIT_TO_CANDIDATE,
    WAIT_PAUSE_ACK
} wait_t;
#define FLAG_AT 0x1
#define FLAG_GETID 0x2
#define FLAG_WAIT_SEND 0x4
#define FLAG_SET(flag) flags |= flag
#define FLAG_CLEAR(flag) flags &= ~flag

uint8_t flags = 0;
#define FLAG_RST flags = 0

#define QUEUE_SIZE 4
const uint8_t QUEUE_MASK = QUEUE_SIZE - 1;
uint8_t event_delay[QUEUE_SIZE];
uint8_t event_type[QUEUE_SIZE];
uint8_t queue_len = 0, cur_event = 0;
#define EVENT_RST queue_len = 0

void add_event(wait_t wait_type, uint8_t delay)
{
    if (queue_len >= QUEUE_MASK)
        return;
    i = cur_event & QUEUE_MASK;
    l = (i + queue_len) & QUEUE_MASK;
    for (; i != l; i = (i + 1) & QUEUE_MASK)
    {
        if (delay < event_delay[i])
        {
            event_delay[i] -= delay;
            break;
        }
        delay -= event_delay[i];
    }
    for (j = l; j != i; j = k)
    {
        k = (j + QUEUE_MASK) & QUEUE_MASK;
        event_delay[j] = event_delay[k];
        event_type[j] = event_type[k];
    }
    event_delay[i] = delay;
    event_type[i] = wait_type;
    ++queue_len;
}

void del_event(wait_t wait_type)
{
    i = cur_event & QUEUE_MASK;
    l = (i + queue_len) & QUEUE_MASK;
    for (; i != l; i = k)
    {
        k = (i + 1) & QUEUE_MASK;
        if (event_type[i] == wait_type)
        {
            for (event_delay[k] += event_delay[i]; i != l; i = k)
            {
                k = (i + 1) & QUEUE_MASK;
                event_delay[i] = event_delay[k];
            }
            --queue_len;
            return;
        }
    }
}

#endif